#pragma once

#include <vector>
#include <map>
#include <memory>
#include "net/libgo.h"
#include "libzqutil/log.h"
#include "libzqutil/ustring.h"

#include "net/msgpack.h"
#include "net/client_impl.h"
#include "libzqutil/ini/SimpleIni.h"
#include "json11/json11.hpp"

#ifdef WIN32

#define dll_ex ".dll"
#define open_dll(dll) LoadLibraryA(dll)
#define get_proc(hdll, func) GetProcAddress(hdll, func)
#define close_dll(dll) FreeLibrary((HINSTANCE)dll)
#define dll_error() GetLastError()
#else

#include <dlfcn.h> 

#define dll_ex ".so"
#define open_dll(dll) dlopen(dll, RTLD_LAZY)
#define get_proc(hdll, func) dlsym(hdll, func)
#define close_dll(dll) dlclose(dll)
#define dll_error() dlerror()

#endif // WIN32

namespace libzq
{
	template<typename Protocol>
	class Client :
		public ClientImpl
	{
	public:
		void on_connect_fail() override;
		std::tuple<bool, co_chan<Protocol>> call(const MsgPacker& msg);
		void expire(const string& msgid);
		void on_handle(std::shared_ptr<MsgPacker> msg) override;
		void on_stop(const ZQError& err) override;
	private:
		co_mutex _mutex;
		map<string, co_chan<Protocol>> _reqs;
	};


	template<typename Protocol>
	inline void Client<Protocol>::on_connect_fail()
	{
		if (_connect_status != CONN_STATUS_CONNECTING && !_stop)
			reconnect();
	}

	template<typename Protocol>
	inline std::tuple<bool, co_chan<Protocol>> Client<Protocol>::call(const MsgPacker & msg)
	{
		string&& msgid = msg.msgid();
		bool status = true;
		co_chan<Protocol> chan(1);
		
		{
			std::lock_guard<co_mutex> lock(_mutex);
			_reqs[msgid] = chan;
		}
		
		status = write(msg);
		return std::tie(status, chan);
	}

	template<typename Protocol>
	inline void Client<Protocol>::expire(const string & msgid)
	{
		std::lock_guard<co_mutex> lock(_mutex);
		auto itor = _reqs.find(msgid);
		if (itor != _reqs.end())
		{
			_reqs.erase(itor);
		}
		LOGT("Request %s expired", msgid);
	}

	template<typename Protocol>
	inline void Client<Protocol>::on_handle(std::shared_ptr<MsgPacker> msg)
	{
		string&& msg_ = msg->msg_body();
		string err;
		auto resp = Protocol();
		resp.parse(msg_, err);
		if (!err.empty())
		{
			LOGE("Parse resp error [%s]", err);
			return;
		}
		std::string&& msgid = msg->msgid();

		std::lock_guard<co_mutex> lock(_mutex);
		auto itor = _reqs.find(msgid);
		if (itor != _reqs.end())
		{
			auto chan = itor->second;
			chan << resp;
			_reqs.erase(itor);
			LOGT("recv msgid %s response", msgid);
		}
		else
		{
			LOGE("Reqid [%s] not found", msgid);
		}
	}

	template<typename Protocol>
	inline void Client<Protocol>::on_stop(const ZQError & err)
	{
		if (err == ERROR_STOP_SERVICE || _stop)
		{
			ClientImpl::stop(err);
		}
		else
		{
			reconnect();
		}
	}

}
